package com.passion.fs.service.impl;

import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import com.passion.common.constant.CommonConstant;
import com.passion.common.enums.BaseEnums;
import com.passion.common.exception.MyException;
import com.passion.common.util.FileUtil;
import com.passion.common.util.StringUtils;
import com.passion.fs.config.LocalFsConfig;
import com.passion.fs.context.LocalFsContext;
import com.passion.fs.entity.FileInfo;
import com.passion.fs.service.LocalFsService;
import com.passion.fs.util.FileEncDecUtil;
import com.passion.fs.vo.FileInfoVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 * 类描述：本地文件系统服务实现
 *
 * @author ZhuYin
 * @since 2025年01月06日
 */
@Slf4j
@ConditionalOnBean(LocalFsConfig.class)
@Service
public class LocalFsServiceImpl implements LocalFsService {

    private static final String DEFAULT_BUCKET = "default";

    /**
     * 判断存储桶是否在存在
     *
     * @param bucket 桶名称
     * @return true 存在 false 不存在
     */
    @Override
    public boolean bucketExists(String bucket) {
        File file = new File(LocalFsConfig.directory + File.separator + bucket);
        return file.exists();
    }

    /**
     * 创建存储桶
     *
     * @param bucket 桶名称
     */
    @Override
    public void makeBucket(String bucket) {
        try {
            if (!bucketExists(bucket)) {
                File file = new File(LocalFsConfig.directory + File.separator + bucket);
                file.mkdirs();
            }
        } catch (Exception e) {
            log.error("[Local] makeBucket Exception:{}", e.getMessage());
            throw new MyException("创建存储桶失败");
        }
    }

    /**
     * 上传文件
     *
     * @param file 上传的文件
     * @return 文件基本信息
     */
    @Override
    public FileInfoVO upload(String userId, MultipartFile file) {
        // 上传文件上传到默认桶中
        return upload(userId, DEFAULT_BUCKET, file);
    }

    /**
     * 上传文件
     * @param userId 用户ID
     * @param bucket 要上传到的桶名称
     * @param file   上传的文件
     * @return 文件基本信息
     */
    @Override
    public FileInfoVO upload(String userId, String bucket, MultipartFile file) {
        if (StringUtils.isBlank(bucket)) {
            // 默认上传到配置的根目录下的名为 default 的桶中
            bucket = DEFAULT_BUCKET;
        }
        String targetLocation = LocalFsConfig.directory + File.separator + bucket;
        makeBucket(targetLocation);
        FileInfoVO fileInfoVo = FileInfoVO.build(file);
        try {
            Path targetLocationPath = Paths.get(targetLocation).resolve(Paths.get(fileInfoVo.getFileSysName()));
            if (!Files.exists(targetLocationPath)) {
                Files.createDirectories(targetLocationPath.getParent());
            }
            file.transferTo(targetLocationPath);
            File sourceFile = new File(targetLocationPath.toString());
            if (LocalFsConfig.encrypt) {
                // 上传的文件加密
                String absolutePath = sourceFile.getAbsolutePath();
                // 将源文件加密存储到文件系统中
                String secretKey = FileEncDecUtil.getSecretKey();
                // 先将源文件重命名
                String rename = absolutePath + ".origin";
                FileUtil.rename(sourceFile, rename, false, true);
                // 将加密文件当做源文件存在文件系统中
                FileEncDecUtil.encrypt(secretKey, new File(rename), new File(absolutePath));
                // 删除源文件
                FileUtil.del(rename);
                // 保存密钥串
                fileInfoVo.setSecretKey(secretKey);
            }

            String url = getUrl(fileInfoVo.getFileSysName());
            fileInfoVo.setAccessUrl(url);
            fileInfoVo.setFilePath(FileUtil.sopPath(targetLocationPath.toString()));
            String fileMd5 = FileUtil.fileMd5(sourceFile);
            fileInfoVo.setFileMd5(fileMd5);

            return fileInfoVo;
        } catch (Exception e) {
            log.error("[Local] file upload failed: {}", e.getMessage());
            throw new MyException("文件上传失败");
        }
    }

    /**
     * 删除文件
     *
     * @param objectName 对象名称
     */
    @Override
    public void delete(String objectName) {
        if (StringUtils.isBlank(objectName)) {
            throw new MyException("系统文件名称不能为空");
        }
        try {
            FileInfo fileInfo = LocalFsContext.fileInfoService.getFileInfo(objectName);
            if (fileInfo != null) {
                fileInfo.setDelFlag(BaseEnums.DelFlag.DELETED.getCode());
                // 逻辑删除
                LocalFsContext.fileInfoService.updateById(fileInfo);
            }
        } catch (Exception e) {
            log.error("[Local] file delete failed: {}", e.getMessage());
            throw new MyException("文件删除失败");
        }
    }

    /**
     * 下载文件
     *
     * @param objectName 对象名称
     * @param response   响应
     */
    @Override
    public void download(String objectName, HttpServletResponse response) {
        // 如果传的是 系统文件名，则去掉后缀后即为id
        if (StringUtils.isNotBlank(objectName) && objectName.contains(".")) {
            objectName = objectName.substring(0, objectName.lastIndexOf("."));
        }
        LocalFsContext.fileInfoService.download(objectName, response);
    }

    /**
     * 获取文件访问地址
     *
     * @param objectName 对象名称，上传文件在文件系统中的名称
     * @return 返回对象的访问地址
     */
    @Override
    public String getUrl(String objectName) {
        // 使用endpoint, 一般是http://本机ip:本机port/mapping/fileName
        return LocalFsConfig.endpoint + "/files/" + objectName;
    }

    /**
     * 批量上传文件
     *
     * @param userId     用户ID
     * @param dirId      目录ID
     * @param files      文件数组
     * @return 返回上传之后的文件集合信息
     * @author ZhuYin
     * @since 2025年01月06日
     */
    @Override
    public List<FileInfoVO> uploadFiles(String userId, String dirId, MultipartFile[] files) {
        return uploadFiles(userId, dirId, DEFAULT_BUCKET, files);
    }

    /**
     * 批量上传文件
     *
     * @param userId     用户ID
     * @param dirId      目录ID
     * @param bucketName 桶名称
     * @param files      文件数组
     * @return 返回上传之后的文件集合信息
     * @author ZhuYin
     * @since 2025年01月06日
     */
    @Override
    public List<FileInfoVO> uploadFiles(String userId, String dirId, String bucketName, MultipartFile[] files) {
        if (files == null || files.length == 0) {
            throw new MyException("文件不能为空");
        }
        if (StringUtils.isBlank(bucketName)) {
            bucketName = DEFAULT_BUCKET;
        }
        dirId = this.getCurrentDirId(dirId);
        String parentId = this.getUploadFileParentId(userId, dirId, bucketName);

        if (!CommonConstant.ROOT_PARENT_ID.equals(parentId)) {
            FileInfo parentInfo = LocalFsContext.fileInfoService.getById(parentId);
            if (parentInfo == null) {
                throw new MyException("目录已不存在");
            }
            bucketName = parentInfo.getBucketName();
        }

        List<FileInfoVO> list = Lists.newArrayList();
        FileInfoVO fileInfoVO = null;
        try {
            for (MultipartFile file : files) {
                fileInfoVO = upload(userId, bucketName, file);
                // 设置该文件上传后所在的存储桶名称
                fileInfoVO.setBucketName(bucketName);
                FileInfo fileInfo;
                // 如果不允许文件重复上传，则查文件md5值
                if (!LocalFsConfig.allowFileDuplicate) {
                    // 文件md5标识
                    String fileMd5 = fileInfoVO.getFileMd5();
                    // 根据文件md5值查询是否已上传过该文件(避免文件系统中存在太多重复的相同文件，从而节省磁盘空间)
                    fileInfo = LocalFsContext.fileInfoService.getOne(Wrappers.<FileInfo>lambdaQuery().eq(FileInfo::getFileMd5, fileMd5).last("limit 1"));
                    if (fileInfo != null) {
                        // 已上传过该文件，则删除掉此次上传的文件
                        FileUtil.del(Paths.get(fileInfoVO.getFilePath()));
                        // 直接将已上传到文件系统的文件位置及访问地址取到
                        String filePath = fileInfo.getFilePath();
                        fileInfoVO.setFilePath(filePath);
                    }
                }
                fileInfoVO.setParentId(parentId);
                fileInfoVO.setOriginalName(file.getOriginalFilename());
                fileInfoVO.setUserId(userId);
                // 将VO转成实体类
                fileInfo = fileInfoVO.buildFileInfo();
                boolean result = LocalFsContext.fileInfoService.save(fileInfo);
                if (result) {
                    list.add(fileInfoVO);
                } else {
                    list.add(FileInfoVO.builder().originalName(file.getOriginalFilename()).fileSize(file.getSize()).errorMsg("文件对象保存失败").build());
                    if (StringUtils.isNotBlank(fileInfoVO.getFilePath())) {
                        // 删除可能已经上传的文件
                        FileUtil.del(fileInfoVO.getFilePath());
                    }
                    log.error("文件：{} 上传失败", file.getOriginalFilename());
                }
            }
        } catch (Exception e) {
            log.error("文件上传异常:", e);
            if (fileInfoVO != null && StringUtils.isNotBlank(fileInfoVO.getFilePath())) {
                // 删除可能已经上传的文件
                FileUtil.del(fileInfoVO.getFilePath());
            }
            throw new MyException("文件上传异常，请联系管理员");
        }
        return list;
    }

    /**
     * 方法说明：文件上传时获取上传文件所在的目录id
     *
     * @param userId     上传文件的用户ID
     * @param dirId      前端传的当前目录ID
     * @param bucketName 前端传的文件要存在的存储桶名称
     * @return 返回上传文件所在的目录id
     * @author ZhuYin
     * @since 2025年01月08日
     */
    private String getUploadFileParentId(String userId, String dirId, String bucketName) {
        String parentId = dirId;
        // 当目录id为 0 时，认为文件上传到根目录下的存储桶中，存储桶如果存在则直接使用，不存在则创建
        if (CommonConstant.ROOT_PARENT_ID.equals(dirId)) {
            if (StringUtils.isBlank(bucketName)) {
                return parentId;
            }
            parentId = this.handleDirectory(userId, bucketName);
            if (StringUtils.isNotBlank(parentId)) {
                return parentId;
            }
        }
        return parentId;
    }

    public String handleDirectory(String userId, String directoryName) {
        // 检查字符串是否包含斜杠
        boolean containsSlash = directoryName.contains("/");
        if (!containsSlash) {
            // 创建单个文件夹(即创建桶)
            FileInfo exist = LocalFsContext.fileInfoService.getOne(Wrappers.<FileInfo>lambdaQuery()
                    .eq(FileInfo::getBucketName, directoryName)
                    .eq(FileInfo::getDirFlag, 1)
                    .last("limit 1")
            );
            if (exist != null) {
                // 如果记录存在的，但是是被删除状态的，则重新恢复(相当于重新创建)
                if (exist.getDelFlag().equals(BaseEnums.DelFlag.DELETED.getCode())) {
                    exist.setDelFlag(BaseEnums.DelFlag.NORMAL.getCode());
                    exist.setCreateId(userId);
                    exist.setCreateTime(new Date());
                    LocalFsContext.fileInfoService.updateById(exist);
                }
                return exist.getId();
            }
            FileInfo newBucket = createNewBucket(userId, directoryName);
            return newBucket.getId();
        } else {
            // 处理多级文件夹
            return this.createMultipleDirectories(userId, directoryName);
        }
    }

    private String createMultipleDirectories(String userId, String directoryName) {
        List<String> directories = Arrays.asList(directoryName.split("/"));
        // 初始化父目录ID为null
        String parentDirectoryId = CommonConstant.ROOT_PARENT_ID;

        for (int i = 0; i < directories.size(); i++) {
            String directory = directories.get(i);
            if (i > 0) {

            }

            FileInfo exist = LocalFsContext.fileInfoService.getOne(Wrappers.<FileInfo>lambdaQuery()
                    .eq(FileInfo::getBucketName, directory)
                    .eq(FileInfo::getDirFlag, 1)
                    .last("limit 1")
            );
            if (exist != null) {
                // 说明该目录已存在了，不需要再创建了
                if (exist.getDelFlag().equals(BaseEnums.DelFlag.DELETED.getCode())) {
                    // 如果之前已经被创建过，只是被删除了，这里再将它恢复(相当于重新创建)
                    exist.setDelFlag(BaseEnums.DelFlag.NORMAL.getCode());
                    exist.setCreateId(userId);
                    exist.setCreateTime(new Date());
                    LocalFsContext.fileInfoService.updateById(exist);
                }
                parentDirectoryId = exist.getId();
            } else {
                String fullPath = buildFullPath(directories.subList(0, i + 1));
                // 在数据库中创建记录
                String id = IdUtil.fastSimpleUUID();
                this.createNewFolder(userId, id, parentDirectoryId,
                        directories.subList(0, i + 1).stream().reduce((a, b) -> a + "/" + b).orElse(""),
                        fullPath, directory, directory);

                // 更新父目录ID为当前目录的ID
                parentDirectoryId = id;
            }
        }
        return parentDirectoryId;
    }

    private String buildFullPath(List<String> directories) {
        StringBuilder fullPath = new StringBuilder(LocalFsConfig.directory);
        for (String directory : directories) {
            fullPath.append("/").append(directory);
        }
        return fullPath.toString();
    }



    private String createSingleFolder(String parentId, String parentBucketName, String userId, String bucketName) {
        String newBucketName = parentBucketName + CommonConstant.DIR_SPLIT + bucketName;
        if (bucketExists(newBucketName)) {
            // 如果存储桶已存在，则查库
            FileInfo bucketInfo = LocalFsContext.fileInfoService.getOne(
                    Wrappers.<FileInfo>lambdaQuery()
                            .eq(FileInfo::getBucketName, newBucketName)
                            .eq(FileInfo::getParentId, parentId)
                            .eq(FileInfo::getDirFlag, 1)
                            .eq(FileInfo::getDelFlag, BaseEnums.DelFlag.NORMAL.getCode())
                            .last("limit 1")
            );
            if (bucketInfo != null) {
                // 库中存在该桶记录，则直接使用该记录
                parentId = bucketInfo.getId();
            } else {
                // 库中不存在该桶记录，则创建该桶记录
                FileInfo fileInfo = createNewBucket(userId, bucketName);
                parentId = fileInfo.getId();
            }
        } else {
            // 文件系统不存在该桶，则创建该桶及数据库记录
            makeBucket(bucketName);
            FileInfo fileInfo = createNewBucket(userId, bucketName);
            parentId = fileInfo.getId();
        }
        return parentId;
    }

    /**
     * 方法说明：在指定文件夹下创建文件夹
     *
     * @param userId     操作用户ID
     * @param dirId      文件夹ID
     * @param folderName 文件夹名称
     * @return 类描述：文件业务对象
     * @author ZhuYin
     * @since 2025年01月08日
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public FileInfo createFolder(String userId, String dirId, String folderName) {
        if (StringUtils.isBlank(dirId)) {
            throw new MyException("目录ID不能为空");
        }
        if (StringUtils.isBlank(folderName)) {
            throw new MyException("文件夹名称不能为空");
        }
        dirId = this.getCurrentDirId(dirId);
        LambdaQueryWrapper<FileInfo> wrapper = Wrappers.<FileInfo>lambdaQuery()
                .eq(FileInfo::getParentId, dirId)
                .eq(FileInfo::getShowName, folderName)
                .eq(FileInfo::getDirFlag, 1)
                .eq(FileInfo::getDelFlag, BaseEnums.DelFlag.NORMAL.getCode())
                .last("limit 1");
        FileInfo dirInfo = LocalFsContext.fileInfoService.getOne(wrapper);
        if (dirInfo != null) {
            throw new MyException("该文件夹已存在");
        }
        // 如果在根目录下创建文件夹，则相当于创建桶
        if (CommonConstant.ROOT_PARENT_ID.equals(dirId)) {
            // 创建桶
            return this.createNewBucket(userId, folderName);
        } else {
            // 创建文件夹
            FileInfo currentDirInfo = LocalFsContext.fileInfoService.getById(dirId);
            return this.createNewFolder(userId, currentDirInfo, folderName);
        }

    }

    /**
     * 方法说明：从前端传的目录id中获取当前目录的id
     *
     * @param dirId 前端传过来的目录ID
     * @return 返回当前目录的id
     * @author ZhuYin
     * @since 2025年01月08日
     */
    private String getCurrentDirId(String dirId) {
        if (StringUtils.isBlank(dirId) || CommonConstant.DIR_SPLIT.equals(dirId)) {
            dirId = CommonConstant.ROOT_PARENT_ID;
        } else {
            dirId = dirId.substring(dirId.lastIndexOf(CommonConstant.DIR_SPLIT) + 1);
        }
        return dirId;
    }

    private void createNewFolder(String userId, String id, String parentId, String bucketName, String fullPath, String originalName, String showName) {
        // 数据库记录该桶
        FileInfo dir = new FileInfo();
        dir.setId(id);
        dir.setParentId(parentId);
        dir.setOriginalName(originalName);
        dir.setBucketName(bucketName);
        // 唯一
        dir.setFileSysName(id);
        dir.setDirFlag(1);
        dir.setCreateId(userId);
        dir.setShowName(showName);
        dir.setFileType(CommonConstant.DIR);
        dir.setFileExt(CommonConstant.DIR);
        dir.setFilePath(fullPath);
        
        boolean result = LocalFsContext.fileInfoService.save(dir);
        if (result) {
            File newDir = new File(dir.getFilePath());
            if (!newDir.exists()) {
                newDir.mkdirs();
            }
        }
    }

    /**
     * 方法说明：创建新的文件夹
     *
     * @param userId        当前操作用户id
     * @param currentDir    当前文件夹对象
     * @param folderName    要创建的文件夹名称
     * @return 【t_fs_file_info】表实体类;
     * @author ZhuYin
     * @since 2025年01月08日
     */
    private FileInfo createNewFolder(String userId, FileInfo currentDir, String folderName) {
        // 数据库记录该桶
        FileInfo dir = new FileInfo();
        String id = IdUtil.fastSimpleUUID();
        dir.setId(id);
        dir.setParentId(currentDir.getId());
        dir.setOriginalName(folderName);
        dir.setBucketName(currentDir.getBucketName() + "/" + folderName);
        dir.setFileSysName(id);
        dir.setDirFlag(1);
        dir.setCreateId(userId);
        dir.setShowName(folderName);
        dir.setFileType(CommonConstant.DIR);
        dir.setFileExt(CommonConstant.DIR);
        dir.setFilePath(FileUtil.sopPath(currentDir.getFilePath() + "/" + folderName));
        boolean result = LocalFsContext.fileInfoService.save(dir);
        if (result) {
            File newDir = new File(dir.getFilePath());
            if (!newDir.exists()) {
                newDir.mkdirs();
            }
            return dir;
        }
        return null;
    }


    private FileInfo createNewBucket(String userId, String bucketName) {
        // 数据库记录该桶
        FileInfo bucket = new FileInfo();
        String bucketId = IdUtil.fastSimpleUUID();
        bucket.setId(bucketId);
        bucket.setParentId(CommonConstant.ROOT_PARENT_ID);
        bucket.setOriginalName(bucketName);
        bucket.setBucketName(bucketName);
        bucket.setFileSysName(bucketId);
        bucket.setDirFlag(1);
        bucket.setCreateId(userId);
        bucket.setShowName(bucketName);
        bucket.setFileType("bucket");
        bucket.setFilePath(FileUtil.sopPath(LocalFsConfig.directory + "/" + bucketName));
        boolean result = LocalFsContext.fileInfoService.save(bucket);
        if (result) {
            File newDir = new File(bucket.getFilePath());
            if (!newDir.exists()) {
                newDir.mkdirs();
            }
        }
        return bucket;
    }


}
